#include "minesweeper.h"

#include <google/protobuf/util/time_util.h>

minesweeper::minesweeper(std::shared_ptr<player> p1, std::shared_ptr<player> p2, 
    int row_num, int col_num, int bombs_num) :
    cells_grid_(static_cast<std::size_t>(row_num), std::vector<cell>(col_num)),
    bombs_num_(bombs_num),
    cell_not_bomb_opened_num_(0),
    p1_(p1),
    p2_(p2)
{
    /* 初始化随机数种子 */
    srand(static_cast<uint32_t>(time(nullptr)));   // TODO: 换成c++随机数
    game_record_.set_player1userid(p1->get_user()->uid());
    game_record_.set_player2userid(p2->get_user()->uid());
}

void minesweeper::caculate_score(std::shared_ptr<player> player,
    const pos& pos_clicked, const std::vector<pos>& opened_pos)
{
    if (cell_at_pos(pos_clicked).is_bomb()) {
        player->add_socre(GAME_SOCRE_BOMB_OPENED);
        return;
    }

    player->add_socre(opened_pos.size() * GAME_SOCRE_NOT_BOMB_OPENED);
}

std::vector<pos> minesweeper::left_buttom_clicked(
    std::shared_ptr<player> player, const pos& pos_clicked, bool &is_game_over)
{
    auto now = std::chrono::system_clock::now();

    std::vector<pos> opened_pos;
    _left_buttom_clicked(player, pos_clicked, opened_pos, is_game_over);
    if (player != nullptr) {
        caculate_score(player, pos_clicked, opened_pos);
    }

    auto left_clicked_event = game_record_.add_leftbuttoncliekedevents();
    *(left_clicked_event->mutable_leftbuttonclickedtime()) = google::protobuf::util::TimeUtil::TimeTToTimestamp(
        std::chrono::system_clock::to_time_t(now)
    );
    if (player == nullptr) {
        left_clicked_event->set_playerindex(LeftButtonClickedProto_PlayerIndex_System);
    } else if (player == p1_) {
        left_clicked_event->set_playerindex(LeftButtonClickedProto_PlayerIndex_PLAYER_1);
    } else if (player == p2_) {
        left_clicked_event->set_playerindex(LeftButtonClickedProto_PlayerIndex_PLAYER_2);
    }
    auto clicked_pos = left_clicked_event->mutable_clickedpos();
    clicked_pos->set_row(pos_clicked.row());
    clicked_pos->set_col(pos_clicked.col());

    for (const auto& pos : opened_pos) {
        auto pos_to_add = left_clicked_event->add_openedpos();
        pos_to_add->set_row(pos.row());
        pos_to_add->set_col(pos.col());
    }

    return opened_pos;
}

/*  */
int minesweeper::init_game(std::vector<pos> &bombs_pos, std::vector<pos> &pos_opened_by_system)
{
    int ret;
    pos init_pos;

    /* 初始化时间 */
    start_time_ = std::chrono::system_clock::now();
    *game_record_.mutable_starttime() = google::protobuf::util::TimeUtil::TimeTToTimestamp(
        std::chrono::system_clock::to_time_t(start_time_)
    );

    ret = rand_pos(init_pos, row_num(), col_num());
    if (ret != MS_OK) {
        printf("generate rand pos failed! ret=%d, row_num=%d col_num=%d\n",
               ret, row_num(), col_num());
        return ret;
    }

    ret = generate_bombs(init_pos, bombs_pos);
    if (ret != MS_OK) {
        printf("generare boms failed! ret=%d", ret);
        return ret;
    }

    for (const auto &pos : bombs_pos) {
        auto pos_to_add = game_record_.add_initbombspos();
        pos_to_add->set_row(pos.row());
        pos_to_add->set_col(pos.col());
    }

    set_all_cells_bombs_around();

    bool is_game_over = false;
    pos_opened_by_system = left_buttom_clicked(nullptr, init_pos, is_game_over);

    /* TODO: 处理极小概率事件，游戏初始化完成后，游戏即结束 */

    printf("sys open init pos (%d, %d)\n", init_pos.row(), init_pos.col());
    for (const auto& pos : pos_opened_by_system) {
        printf("(%d, %d) ", pos.row(), pos.col());
    }
    printf("\n");

    print_bombs_around(init_pos);
    printf("\n");
    print_cell_state(init_pos);

    return ret;
}

/* 生成bombs_bum个地雷，填充到地图中 */
int minesweeper::generate_bombs(const pos& init_pos, std::vector<pos> &bombs_pos)
{
    int ret;
    int num = 0;
    pos cur_pos;

    /* TODO: 此处目前仅做简单检验 */
    if (bombs_num_ >= cell_num() - 9) {
        return MS_E_PARAM;
    }

    ret = rand_pos(cur_pos, row_num(), col_num());
    if (ret != MS_OK) {
        printf("genereta a rand_pos failed! ret = %d\n", ret);
        return ret;
    }

    bombs_pos.clear();
    while (num < bombs_num_) {
        /* 生成的pos不能已经是地雷，也不能和初始位置相邻 */
        while (is_bomb(cur_pos) || cur_pos == init_pos || pos::is_adjacent(cur_pos, init_pos)) {
            ret = rand_pos(cur_pos, row_num(), col_num());
            if (ret != MS_OK) {
                printf("genereta a rand_pos failed! ret = %d\n", ret);
                return ret;
            }
        }

        ret = set_as_bomb(cur_pos);
        if (ret != MS_OK) {
            printf("set pos as bomb failed! ret=%d, pos=(%d, %d)\n",
                   ret, row_num(), col_num());
            return ret;
        }
        bombs_pos.push_back(cur_pos);
        num++;
    }

    return ret;
}

bool minesweeper::is_pos_vaild(const pos& pos) const
{
    if (pos.row() < 0 || pos.row() >= row_num()) {
        return false;
    }

    if (pos.col() < 0 || pos.col() >= col_num()) {
        return false;
    }

    return true;
}

bool minesweeper::is_bomb(const pos& pos) const
{
    if (!is_pos_vaild(pos)) {
        return false;
    }

    return cells_grid_[pos.row()][pos.col()].is_bomb();
}

/* 内部调用保证pos合法 */
cell& minesweeper::cell_at_pos(const pos& pos)
{
    return cells_grid_[pos.row()][pos.col()];
}

int minesweeper::set_as_bomb(const pos& pos)
{
    cells_grid_[pos.row()][pos.col()].set_as_bomb();
    return MS_OK;
}

int minesweeper::rand_pos(pos& pos, int row_num, int col_num) const
{
    if ((row_num == 0) || (col_num == 0)) {
        return MS_E_PARAM;
    }
    int rand_num = rand() % (row_num * col_num);    // TODO: 整数溢出

    pos.set_row(rand_num / col_num);
    pos.set_col(rand_num % col_num);

    return MS_OK;
}

int minesweeper::bombs_around(const pos& pos) const
{
    int num = 0;

    for (int i = -1; i <= 1; i++) {
        for (int j = -1; j <= 1; j++) {
            if (i == 0 && j == 0) {
                continue;
            }
            int row = pos.row() + i;
            int col = pos.col() + j;

            if (row < 0 || row >= row_num()) {
                continue;
            }

            if (col < 0 || col >= col_num()) {
                continue;
            }

            if (cells_grid_[row][col].is_bomb()) {
                num++;
            }
        }
    }

    return num;
}

void minesweeper::set_all_cells_bombs_around()
{
    for (int i = 0; i < row_num(); i++) {
        for (int j = 0; j < col_num(); j++) {
            auto& c = cells_grid_[i][j];
            int num = bombs_around(pos(i, j));
            c.set_bombs_around(num);
        }
    }
}

/* TODO: 待完善 */
void minesweeper::_left_buttom_clicked(std::shared_ptr<player> player,
                                       const pos& pos_clicked,
                                       std::vector<pos>& opened_pos,
                                       bool &if_game_over)
{
    if (!is_pos_vaild(pos_clicked)) {
        return;
    }

    cell &cell_clicked = cell_at_pos(pos_clicked);
    if (cell_clicked.state() != cell::ORIGINAL) {
        return;
    }

    opened_pos.push_back(pos_clicked);

    if (player == p1_) {
        cell_clicked.set_state(cell::OPENED_BY_PLAYER1);
    } else if (player == p2_) {
        cell_clicked.set_state(cell::OPENED_BY_PLAYER2);
    } else {
        cell_clicked.set_state(cell::OPENED_BY_SYSTEM);
    }

    if (is_bomb(pos_clicked)) {
        return;
    }

    cell_not_bomb_opened_num_++;
    if (this->is_game_over()) {
        if_game_over = true;

        /* 记录游戏结束时间 */
        end_time_ = std::chrono::system_clock::now();
        *game_record_.mutable_endtime() = google::protobuf::util::TimeUtil::TimeTToTimestamp(
            std::chrono::system_clock::to_time_t(end_time_)
        );
        return;
    }

    if (bombs_around(pos_clicked) != 0) {
        return;
    }

    for (int i = -1; i <= 1; i++) {
        for (int j = -1; j <= 1; j++) {
            if (i == 0 && j == 0) {
                continue;
            }
            int cur_row = pos_clicked.row() + i;
            int cur_col = pos_clicked.col() + j;
            _left_buttom_clicked(player, pos(cur_row, cur_col), opened_pos, if_game_over);
        }
    }

    return;
}
